package com.google.code.b0rx0r.advancedSamplerEngine;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;


/** The play queue is the place where playables are enqueued for playing. 
 *  The vst interface and gui place them here, the AudioProcessor reads them from here.
 *  
 * @author Lui
 *
 */
public class AudioProcessor {
	private long now = 0;
	private float[] empty = new float[1024]; 
	
	/* This is guaranteed to be ordered so that no EE with an earlier start time comes before 
	 * an EE with a later start time. EEs with equal start times are ordered arbitrarily. */
	private SortedSet<EnqueuedEnqueueable> queue = new TreeSet<EnqueuedEnqueueable>();
  
	private void increaseTime(int amount) {
  	now += amount;
  }

	public long getTime() {
		return now;
	}
	
	public EnqueuedEnqueueable enqueue(Enqueueable enqueueable, long startTime) {
		EnqueuedEnqueueable ee = new EnqueuedEnqueueable(enqueueable, startTime);
		queue.add(ee);
		return ee;
	}
	
	public void dequeue(EnqueuedEnqueueable ee) {
		queue.remove(ee);
	}
	
	
	/* we re-use the list of EEs to dequeue. while this makes stuff less clear, it saves us instantiating a new list for each call to processReplacing. */ 
	private List<EnqueuedEnqueueable> finished = new LinkedList<EnqueuedEnqueueable>();
	
	public void processReplacing(float[][] inputs, float[][] outputs, int sampleFrames) {
		//TODO deal with sampleFrames > empty.length!!
		for (int i = 0; i<outputs.length; i++) {
			if (outputs[i] != null) {
  		  System.arraycopy(empty, 0, outputs[i], 0, sampleFrames);
			}
		}
		
		for (EnqueuedEnqueueable ee : new ArrayList<EnqueuedEnqueueable>(queue)) {
			//the EEs are sorted, so if we hit upon one that wont start until at least the next call, we can stop processing.
			if (ee.start > now + sampleFrames)
				break;

			//after handling all the EEs we dequeue all finished EEs.
			boolean keep = handleEnqueueable(ee, now, outputs, sampleFrames);
			if (!keep) {
				finished.add(ee);
			}
		}
		
		//dequeue the finished slices
		queue.removeAll(finished);
		finished.clear();
		
		//now is later!
		increaseTime(sampleFrames);
	}
	
	
	
	
	
	private boolean handleEnqueueable(EnqueuedEnqueueable ee, long now, float[][] outputs, int sampleFrames) {
		Enqueueable e = ee.getEnqueueable();
		
		long start = ee.getStart();
		long offsetIntoEnqueueable = now - start;
		
		//set global offset for the modulations
    e.setModulationGlobalOffset(start);

		//if its not starting until later (shouldn't happen!) dont play it!
		if (offsetIntoEnqueueable < 0)
			return true;

		for (int i = 0; i<sampleFrames; i++) {
			long offset = i + offsetIntoEnqueueable;
			
			for (int channel = 0; channel < e.getChannelCount(); channel++) {
				for (int output : e.getOutputMap().getOutputs(channel)) {
					float data = e.getAudioData(channel, offset);
					
					if (data == Enqueueable.NO_MORE_AUDIO_DATA)
					  return false;
			  	
					outputs[output][i] += data; 
			  }
			}
		}
		
		return true;
		
	}
	public static class EnqueuedEnqueueable implements Comparable<EnqueuedEnqueueable> {
		private Enqueueable enqueueable;
		private long start;
//TODO		we probably want "more" parameters here anyway? private int velocity;

		public EnqueuedEnqueueable(Enqueueable enqueueable, long start) {
			this.enqueueable = enqueueable;
			this.start = start;
		}

		public Enqueueable getEnqueueable() {
			return enqueueable;
		}

		public long getStart() {
			return start;
		}
	
		@Override
		public int compareTo(EnqueuedEnqueueable other) {
			if (other == this)
				return 0;

			//same time, but we need to make the result non-0 so they are treated as distinct!
			if (start == other.start) {
				return System.identityHashCode(this) - System.identityHashCode(other);
			}
			
			return (int)(start - other.start);
		}
	
	}


}

